#include "stdlib.hpp"
#include <purewell/socket.hpp>

namespace purewell
{

#ifndef WIN32
#	define __PWX(x)		(x)
#	define __PWXC(x)	(x)
#else
#	define __PWX(x)		((char*)(x))
#	define __PWXC(x)	((const char*)(x))
#endif

#ifdef WIN32
bool
PWSocket::initializeSocketLibrary(int v1, int v2)
{
	WORD ver(MAKEWORD(v1, v2));
	WSADATA data;

	return ( WSAStartup(ver, &data) == 0 );
}

void
PWSocket::releaseSocketLibrary(void)
{
	WSACleanup();
}

#endif

PWSocket::PWSocket()
{
}

PWSocket::~PWSocket()
{
}

void
PWSocket::destroy(void)
{
	int fd(detach());
	if ( -1 != fd )
	{
#ifndef WIN32
		::close(fd);
#else
		::closesocket(fd);
#endif
	}
}

bool
PWSocket::initialize(const char* name, int omode, int perm)
{
	_PWTRACE("%s","DO NOT USE THIS FUNCTION");
	abort();
	return false;
}

PWSocketAddress&
PWSocket::getPeerName(PWSocketAddress& addr) const
{
	addr.clear();
	struct sockaddr_storage sa;
	socklen_t slen(sizeof(sa));
	memset(&sa, 0x00, sizeof(sa));

	if ( 0 == getpeername(m_fd, (struct sockaddr*)&sa, &slen) )
	{
		addr.assign((struct sockaddr*)&sa, slen);
	}

	return addr;
}

PWSocketAddress&
PWSocket::getSocketName(PWSocketAddress& addr) const
{
	addr.clear();
	struct sockaddr_storage sa;
	socklen_t slen(sizeof(sa));
	memset(&sa, 0x00, sizeof(sa));

	if ( 0 == getsockname(m_fd, (struct sockaddr*)&sa, &slen) )
	{
		addr.assign((struct sockaddr*)&sa, slen);
	}

	return addr;
}

bool
PWSocket::initialize(int family, int socktype, int protocol)
{
	const int fd(socket(family,socktype,protocol));

	if ( -1 == fd ) return false;
	if ( attach(fd) ) return true;

	::close(fd);
	return false;
}

bool
PWSocket::shutdown(int type)
{
	return 0 == ::shutdown(m_fd, type);
}

bool
PWSocket::bind(const PWSocketAddress& addr)
{
	return 0 == ::bind(m_fd,
		(struct sockaddr*)addr.getAddress(), addr.getSize());
}

bool
PWSocket::listen(size_t backlog)
{
	return 0 == ::listen(m_fd, backlog);
}

bool
PWSocket::accept(PWSocket* sock, PWSocketAddress* paddr) const
{
	if ( NULL == sock ) return false;
	return this->accept(*sock, paddr);
}

bool
PWSocket::accept(PWSocket& sock, PWSocketAddress* paddr) const
{
	struct sockaddr* sa(NULL);
	socklen_t slen(0);
	if ( NULL != paddr )
	{
		sa = (struct sockaddr*)paddr->getAddress();
		slen = (socklen_t)paddr->getMaxSize();
	}
#ifdef WIN32
	else
	{
		static struct sockaddr_storage in_sa;
		sa = (struct sockaddr*)&in_sa;
		slen = sizeof(in_sa);
	}
#endif

	const int newfd( ::accept(m_fd, sa, &slen) );

	if ( -1 == newfd ) return false;

	if ( ! sock.attach(newfd) )
	{
		::close(newfd);
		return false;
	}

	return true;
}

bool
PWSocket::accept(int& sock, PWSocketAddress* paddr) const
{
	struct sockaddr* sa(NULL);
	socklen_t slen(0);
	if ( NULL != paddr )
	{
		sa = (struct sockaddr*)paddr->getAddress();
		slen = (socklen_t)paddr->getMaxSize();
	}

	sock = ::accept(m_fd, sa, &slen);
	return -1 != sock;
}

bool
PWSocket::connect(const PWSocketAddress& addr)
{
	return 0 == ::connect(m_fd,
		(struct sockaddr*)addr.getAddress(), addr.getSize());
}

ssize_t
PWSocket::receive(void* buf, size_t blen, int flag)
{
	return ::recv(m_fd, (char*)buf, blen, flag);
}

ssize_t
PWSocket::receiveFrom(void* buf, size_t blen, int flag,
	PWSocketAddress* paddr)
{
	char sa[PWSA_MAX_LEN];
	socklen_t slen(PWSA_MAX_LEN);
	const ssize_t res( ::recvfrom(m_fd, (char*)buf, blen, flag,
		(struct sockaddr*)sa, &slen) );
	if ( NULL != paddr && res >= 0 )
	{
		paddr->assign(sa, slen);
	}

	return res;
}

ssize_t
PWSocket::send(const void* buf, size_t blen, int flag)
{
	return ::send(m_fd, (const char*)buf, blen, flag);
}

ssize_t
PWSocket::sendTo(const void* buf, size_t blen, int flag,
	const PWSocketAddress* paddr)
{
	return ( ::sendto(m_fd, (const char*)buf, blen, flag,
		(struct sockaddr*)paddr->getAddress(),
		paddr->getSize()) );
}

ssize_t
PWSocket::read(void* buf, size_t blen)
{
	return ::recv(m_fd,(char*)buf,blen,0);
}

ssize_t
PWSocket::write(const void* buf, size_t blen)
{
	return ::send(m_fd,(const char*)buf,blen,0);
}

bool
PWSocket::getOption(void* v, size_t* vlen, int lv, int optname) const
{
	socklen_t olen;

	if ( 0 == getsockopt(m_fd, lv, optname, __PWX(v), &olen) )
	{
		if ( vlen ) *vlen = (size_t)olen;
		return true;
	}

	return false;
}

bool
PWSocket::setOption(const void* v, size_t vlen, int lv, int optname)
{
	return 0 == setsockopt(m_fd, lv, optname, __PWXC(v), (socklen_t)vlen);
}

bool
PWSocket::getOptionBoolean(bool& v, int lv, int optname) const
{
	int ret;
	socklen_t retsize(sizeof(ret));
	if ( 0 == getsockopt(m_fd, lv, optname, __PWX(&ret), &retsize) )
	{
		v = (ret != 0);
		return true;
	}

	return false;
}

bool
PWSocket::setOptionBoolean(bool v, int lv, int optname)
{
	int ret(v?1:0);
	return 0 == setsockopt(m_fd, lv, optname, __PWXC(&ret), sizeof(ret));
}

bool
PWSocket::getOptionInteger32(int32_t& v, int lv, int optname) const
{
	socklen_t retsize(sizeof(v));
	return 0 == getsockopt(m_fd, lv, optname, __PWX(&v), &retsize);
}

bool
PWSocket::setOptionInteger32(int32_t v, int lv, int optname)
{
	return 0 == setsockopt(m_fd, lv, optname, __PWXC(&v), sizeof(v));
}

bool
PWSocket::getOptionInteger64(int64_t& v, int lv, int optname) const
{
	socklen_t retsize(sizeof(v));
	return 0 == getsockopt(m_fd, lv, optname, __PWX(&v), &retsize);
}

bool
PWSocket::setOptionInteger64(int64_t v, int lv, int optname)
{
	return 0 == setsockopt(m_fd, lv, optname, __PWXC(&v), sizeof(v));
}

bool
PWSocket::getOptionReuseAddress(void) const
{
	bool res;
	if ( getOptionBoolean(res, SOL_SOCKET, SO_REUSEADDR) )
	{
		return res;
	}

	return false;
}

bool
PWSocket::setOptionReuseAddress(bool v)
{
	return setOptionBoolean(v, SOL_SOCKET, SO_REUSEADDR);
}

bool
PWSocket::getOptionKeepAlive(void) const
{
	bool res;
	if ( getOptionBoolean(res, SOL_SOCKET, SO_KEEPALIVE) )
	{
		return res;
	}

	return false;
}

bool
PWSocket::setOptionKeepAlive(bool v)
{
	return setOptionBoolean(v, SOL_SOCKET, SO_KEEPALIVE);
}

size_t
PWSocket::getOptionReceiveBuffer(void) const
{
	int32_t v;
	if ( getOptionInteger32(v, SOL_SOCKET, SO_RCVBUF) )
	{
		return (size_t)v;
	}

	return false;
}

bool
PWSocket::setOptionReceiveBuffer(size_t v)
{
	return setOptionInteger32((int32_t)v, SOL_SOCKET, SO_RCVBUF);
}

size_t
PWSocket::getOptionSendBuffer(void) const
{
	int32_t v;
	if ( getOptionInteger32(v, SOL_SOCKET, SO_SNDBUF) )
	{
		return (size_t)v;
	}

	return (size_t)0;
}

bool
PWSocket::setOptionSendBuffer(size_t v)
{
	return setOptionInteger32((int32_t)v, SOL_SOCKET, SO_SNDBUF);
}

bool
PWSocket::getOptionDelay(void) const
{
	bool res;
	if ( getOptionBoolean(res, IPPROTO_TCP, TCP_NODELAY) )
	{
		return !res;
	}

	return true;
}

bool
PWSocket::setOptionDelay(bool v)
{
	return setOptionBoolean(!v, IPPROTO_TCP, TCP_NODELAY);
}

};//namespace purewell

